﻿using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.ServiceModel;
using Xenta.Structs;
using Xenta.Utils;

namespace Xenta.Api
{
    /// <summary>
    /// Represents the base class for the API request and response.
    /// </summary>
    [MessageContract]
    public abstract class ApiMessage : IDisposable
    {
        #region Properties

        /// <summary>
        /// Gets or sets the infrastructure name.
        /// </summary>
        [MessageHeader]
        public string Infrastructure
        {
            get;
            set;
        }

        /// <summary>
        /// Gets or sets the list of the handlers.
        /// </summary>
        [MessageHeader]
        public List<HandlerInfo> Handlers
        {
            get;
            set;
        }

        /// <summary>
        /// Gets or sets the MIME type.
        /// </summary>
        [MessageHeader]
        public string MimeType
        {
            get;
            set;
        }

        /// <summary>
        /// Gets or sets the data stream.
        /// </summary>
        [MessageBodyMember]
        public Stream Data
        {
            get;
            set;
        }

        #endregion

        #region Methods

        /// <summary>
        /// Disposes the associated data stream.
        /// </summary>
        public void Dispose()
        {
            if(Data == null)
                return;
            Data.Dispose();
            Data = null;
        }

        #endregion
    }

    /// <summary>
    /// Represents the API request.
    /// </summary>
    [MessageContract]
    public class ApiRequest : ApiMessage
    {
    }

    /// <summary>
    /// Represents the API response.
    /// </summary>
    [MessageContract]
    public class ApiResponse : ApiMessage
    {
    }

    /// <summary>
    /// Defines the API contract.
    /// </summary>
    [ServiceContract(Namespace = "Xenta.Api")]
    [ServiceKnownType(typeof(HandlerInfo))]
    public interface IApi
    {
        #region Methods

        /// <summary>
        /// Processes the request.
        /// </summary>
        /// <param name="request">The request.</param>
        /// <returns>The response.</returns>
        [OperationContract]
        ApiResponse Process(ApiRequest request);

        #endregion
    }

    /// <summary>
    /// Represents the API base.
    /// </summary>
    public abstract class ApiBase : IApi
    {
        #region Constancts

        private const int BUFFER_SIZE = 4096;

        #endregion

        #region Methods

        public ApiResponse Process(ApiRequest request)
        {
            var infrastructure = Infrastructure(request);
            if(infrastructure == null)
                throw new FaultException("infrastructure");
            if(request == null)
                throw new FaultException("request");

            if(request.Handlers == null || !request.Handlers.Any())
                throw new FaultException(Err.Of(this).NotSpecified("Handler"));
            if(request.Handlers.Any(x => !infrastructure.CheckDependency<IHandler>(x.Guid)))
                throw new FaultException(Err.Of<IHandler>().NotFound());

            try
            {
                string mimeType = StringHelper.EnsureNotNull(request.MimeType);
                var stream = new MemoryStream();

                try
                {
                    request.Data.CopyTo(stream, BUFFER_SIZE);
                    stream.Seek(0, SeekOrigin.Begin);

                    var rspHndList = new List<HandlerInfo>();
                    using(var ioc = infrastructure.DependencyResolver())
                    {
                        foreach(var reqHndInfo in request.Handlers)
                        {
                            var hnd = ioc.Resolve(reqHndInfo.Guid) 
                                as IHandler;
                            var rspHndInfo = new HandlerInfo
                            {
                                Guid = reqHndInfo.Guid,
                                Properties = new Dictionary<String, String>()
                            };
                            var tmpStream = new MemoryStream();

                            try
                            {
                                hnd.Handle(ref mimeType, 
                                    reqHndInfo.Properties, 
                                    stream, 
                                    rspHndInfo.Properties, 
                                    tmpStream);
                            }
                            catch
                            {
                                tmpStream.Dispose();
                                throw;
                            }

                            stream.Dispose();

                            stream = tmpStream;
                            stream.Seek(0, SeekOrigin.Begin);

                            rspHndList.Add(rspHndInfo);
                        }
                    }

                    var rsp = new ApiResponse
                    {
                        Infrastructure = infrastructure.Name,
                        Handlers = rspHndList,
                        MimeType = mimeType,
                        Data = stream.Length != 0 
                            ? new MemoryStream((int)stream.Length) 
                            : Stream.Null
                    };
                    if(stream.Length != 0)
                    {
                        stream.CopyTo(rsp.Data);
                        rsp.Data.Seek(0, SeekOrigin.Begin);
                    }
                    return rsp;
                }
                finally
                {
                    stream.Dispose();
                }
            }
            catch(Exception ex)
            {
                throw new FaultException(ex.Message);
            }
        }

        #endregion

        #region Utilities

        public abstract IInfrastructure Infrastructure(ApiRequest request);

        #endregion
    }
}
